﻿using System.Drawing;
using System;
using FANN.Net;
using System.Collections;
using System.IO;

namespace Bramki
{
    class SSN
    {

        /// <summary>
        /// enum przedstawiający rozpoznawane bramki
        /// bramki sa kodowane przez neurony w SSN w identycznej kolejności
        /// </summary>
        public enum Bramka { OR = 0, AND, XOR, NOT, NAND, NOR };

        /// <summary>
        /// tablica zawierająca opis SSN (ilości neuronów w kolejnych warstwach)
        /// </summary>
        private static uint[] warstwy = { 10*10, 40, 6 };

        //parametry uczenia SSN
        private const uint maxIloscEpok = 2000;
        private const uint odlegloscPomiedzyRaportami = 100;
        private const float maxBlad=0.01f;

        //true-ustawienie włączone/ false wyłączone
        private Object[] szybkoscUczenia = {0.001f,true};
        //x[0]-wartość x[1]-warstwa ukryta x[2]-warstwa wyjściowa
        private Object[] aktywacja = {0.01f,true,true};
        private Object[] funkcjaAktywacji = {ActivationFunction.SigmoidStepWise,true,true};


        /// <summary>
        /// 
        /// </summary>
        /// <param name="sciezka">scieżka do strorzenia/wykorzystywania SSN</param>
        public SSN(String sciezka){
            sciezkaSSN=sciezka;
        }

        /// <summary>
        /// ścieżka do pliku SSN
        /// </summary>
        protected String sciezkaSSN;


        /// <summary>
        /// uczy SSN stosując przykłady znajdujące się w folderze o podanej ścieżce   
        /// pliki muszą mieć odpowiednie nazwy np:
        /// OR1.bmp, or2.bmp, or003.bmp będą uznawane za bramki OR i SSN będzie uczona w taki sposób
        /// </summary>
        /// <param name="sciezka">ścieżka folderu, z którego będą pobierane bitmapy do uczenia SSN</param>
        public bool uczSSN(String sciezka)
        {
            TrainingData dane = new TrainingData();
            
            //max ilość danych wejściowych (ilość poprawnych danych uczących może byc mniejsza)
            int iloscPlikow = (new DirectoryInfo(@sciezka)).GetFiles("*.bmp").Length;

            if (iloscPlikow == 0)
                return false;

            uint iloscDanych = 0;
            Double[][] wejscie = new Double[iloscPlikow][];
            Double[][] wyjscie = new Double[iloscPlikow][];             
            
            //dla każdej bramki z osobna
            for (int i = 0, j = 0; i < warstwy[2]; ++i)
            {
                //wczytywanie wyłącznie bramek kolejnego typu
                FileInfo[] pliki = (new DirectoryInfo(@sciezka)).GetFiles(((Bramka)i).ToString()+"*.bmp");

                iloscDanych += (uint)pliki.Length;

                //stworzenie wektora wyjść dla danej bramki
                Double[] wy = new Double[warstwy[2]];
                wy[i] = 1.0;

                foreach(FileInfo pl in pliki)
                {   //wczytanie danych przeskalowanego obrazka
                    String pp=pl.FullName;                  
                    Image tt=Image.FromFile(@pp);
                    int tmp = (int)Math.Sqrt(warstwy[0]);
                    Image yy=Obraz.przeskaluj(tt,tmp,tmp);
                    wejscie[j] = Obraz.zamienNaDane(yy);
                    wyjscie[j] = wy;
                    ++j;
                }
            }

            //dodanie danych treningowych
            if (iloscDanych == 0)
                return false;
            dane.SetTrainData(iloscDanych, warstwy[0], wejscie, warstwy[2], wyjscie);
            

            //stworzenie nowej SSN
            NeuralNet ssn = new NeuralNet();
            if (!ssn.CreateStandardArray(warstwy))
                return false;

            //ustawianie pozątkowy wag
            ssn.InitWeights(dane);

            //ustawianie szybkości uczenia się SSN i funkcji stopu
            if((bool)szybkoscUczenia[1])
                ssn.SetLearningRate((float)szybkoscUczenia[0]);
            ssn.SetTrainStopFunction(StopFunction.MSE);
            //funkcja aktywacji i jej stromizna na warstwie wyjściowej
            if((bool)funkcjaAktywacji[1])
                ssn.SetActivationFunctionHidden((ActivationFunction)funkcjaAktywacji[0]);
            if ((bool)funkcjaAktywacji[2])
                ssn.SetActivationFunctionOutput((ActivationFunction)funkcjaAktywacji[0]);
            if((bool)aktywacja[1])
                ssn.SetActivationSteepnessHidden((float)aktywacja[0]);
            if ((bool)aktywacja[1])
                ssn.SetActivationSteepnessOutput((float)aktywacja[0]);            


            //trenowanie SSN
            ssn.TrainOnData(dane, maxIloscEpok, odlegloscPomiedzyRaportami, maxBlad);

            //zapisanie SSN do pliku
            if(!ssn.Save(@sciezkaSSN))
                return false;

            return true;
        }

        /// <summary>
        /// Dokonuje analizy wczytanego (nieprzesklaowanego) obrazu 
        /// </summary>
        /// <param name="sciezka">Obraz do przeskalowania i przemielenia przez SSN</param>
        /// <returns>Zwraca mapę bramka (enum SSN.Bramka), prawdopodobieństwo (double) lub null w przypadku błędu</returns>
        public Hashtable wykonaj(Image obraz)
        {
            //wczytanieSSN
            NeuralNet ssn = new NeuralNet();
            if(!ssn.CreateFromFile(@sciezkaSSN))
                return null;

            //przetowrzenie obrazu
            int tmp = (int)Math.Sqrt(warstwy[0]);
            Image wejscie=Obraz.przeskaluj(obraz,tmp,tmp);

            Double[] dane=Obraz.zamienNaDane(wejscie);

            if(dane.GetLength(0)!=warstwy[0])
                return null;

            //Przepuszczenie przez SSN
            Double[] wyj=ssn.Run(dane);
            Hashtable ret = new Hashtable();                        

            //przetwarzanie wyników (łączenie bramka-wynik)
            int i=0;
            foreach (Double bramka in wyj)
                ret.Add((Bramka)i++, bramka);

            return ret;
        }

    }
}
